/**
 * 
 */
package robocup.component.actionfactories;

import java.util.Collections;
import java.util.LinkedList;

import robocup.component.ControllerNumbers;
import robocup.component.Prophet;
import robocup.component.WorldModel;
import robocup.component.ControllerNumbers.PLAY_MODE;
import robocup.component.ControllerNumbers.TEAM;
import robocup.component.actions.Action;
import robocup.component.actions.SayAction;
import robocup.component.speechacts.MessageFactory;
import robocup.component.tactics.AbstractState.STATES;
import robocup.component.worldobjects.Ball;
import robocup.component.worldobjects.Player;
import robocup.component.worldobjects.Player.RELATIVE;
import robocup.component.worldobjects.Player.ROLE;
import robocup.component.worldobjects.Player.UPDATE_MODE;

/**
 * A factory for creating say-actions. This class should be extended such that
 * each state produces say-actions based on an own logic.
 */
public class SayActionFactory {

  public static class WeightedPlayer
      implements Comparable<WeightedPlayer> {

    public Player player;
    public double weight = 0.0;

    public WeightedPlayer(
        final Player p) {

      this.player = p;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    public int compareTo(final WeightedPlayer o) {

      if (this.weight > o.weight) {
        return 1;
      }
      return -1;
    }

  }

  private static final boolean DEBUG = false;

  /**
   * determines the say action based on the (mainly) state.
   * 
   * @param prophet -
   *          the prophet
   * @param world -
   *          the world
   * @param state -
   *          the state
   * @param mainAction -
   *          the main action !! IF YOU USE ENCODING FUNCTIONS WHICH HAVE NOT
   *          YET BEEN USED YOU WILL HAVE TO ADJUST THE UPDATE METHODS OF THE
   *          BALL AND PLAYERS MODEL!!
   * 
   * @return the say action
   */
  public static SayAction getAction(final Prophet prophet,
      final WorldModel world,
      final STATES state,
      final Action mainAction) {

    final MessageFactory mf = world.getMessageFactory();
    final StringBuffer str = new StringBuffer();

    // action to send
    final SayAction sayAction = new SayAction("");

    // data to send:
    Ball ball = world.getBallRef();
    final Player player = world.getSelfRef().predictPlayerAfterAction(mainAction);

    final LinkedList<Player> playersWithBallControl = SayActionFactory
        .getSeenOpponentsThatCanKick(world);

    // A player, whos recovery value is below 0.6, does say his
    // recovery when !PLAY_ON
    if ((world.getPlayMode() != PLAY_MODE.PLAY_ON) && (player.getRecovery() < 0.7)) {
      if (SayActionFactory.DEBUG) {
        System.out
            .println(world.getCycleAndPlayerString() + " SAYING Recovery + " + player
                .getRecovery());
      }
      str.append(mf.encodeRecovery(player.getNumber(), player.getRecovery()));
      sayAction.setMessage(str.toString());
      return sayAction;
    }

    // A player who kicks will say the ball for 2 cycles. (see
    // AttentionToActionFactory)
    if (world.getSelfRef().getLastState().isKickState() && !world.getSelfRef()
        .canKick(world.getBallRef()) &&
        (prophet.getFastestEnemyToBall() == null || 
        		!prophet.getFastestEnemyToBall().canKick(world.getBallRef()))) {
      if (world.getBallRef().getAge(world.getBodyCycle(),
          Ball.UPDATE_MODE.SEEN_WITHOUT_SPEED) == 0) {
        if (playersWithBallControl.size() > 0) {
          return SayActionFactory.sayDribblingPlayerAndOther(world
              .getBodyCycle(), playersWithBallControl.get(0), world
              .getOpponents(), world.getBallRef(), world.getSelfRef(), mf,
              world, prophet);
        }
        else {
          str.append(mf.encodeCompleteBall(world.getBallRef(), 0));
        }
        sayAction.setMessage(str.toString());
        return sayAction;
      }
    }

    // ball was seen this cycle
    final boolean ballSeen = world.getBallRef().getAge(world.getBodyCycle(),
        robocup.component.worldobjects.Ball.UPDATE_MODE.SEEN_WITHOUT_SPEED) == 0;
    // ball was seen with speed in this cycle (&& for performance)
    boolean ballFullSeen = ballSeen && world.getBallRef().getAge(
        world.getBodyCycle(),
        robocup.component.worldobjects.Ball.UPDATE_MODE.SEEN_FULL) == 0;
    final boolean ballNear = world.getDistance(world.getBallRef()) < 15;

    // goalie always says ball when ball in own half
    if (world.getSelfRef().isGoalie() && !world.canKick()) {

      if (ballSeen && world.getBallRef().getPosition().y < 10) {
        if (playersWithBallControl.size() == 0) {
          str.append(mf.encodeCompleteBall(world.getBallRef(), 0));
          sayAction.setMessage(str.toString());
          return sayAction;
        }
        else {
          return SayActionFactory.sayDribblingPlayerAndOther(world
              .getBodyCycle(), playersWithBallControl.get(0), world
              .getOpponents(), world.getBallRef(), world.getSelfRef(), mf,
              world, prophet);
        }
      }
      else {
    	  return SayActionFactory.sayBestTwoOpponents(world.getBodyCycle(),world.getOpponents(), ball, player, mf, prophet, world, false);
      }
    }

    // 7 always says ball when ball in opponents half
    if ((world.getSelfRef().getRoleNumber() == 7) && !world.canKick() && state != STATES.INTERCEPT_BALL) {

      // always say ball, if somewhat reliable!
      if ((world.getBallRef().getAge(world.getBodyCycle(),
          robocup.component.worldobjects.Ball.UPDATE_MODE.META_BASED_ON_INFO) < 2) && (prophet
          .getInterceptionTime() <= 2)) {
        sayAction.setMessage(mf.encodeCompleteBall(ball, 0));
        return sayAction;
      }

      // else, do as supposed!
      // opponent has ball
      if (prophet.inPossessionOfBall() != TEAM.WE) {
    	// own half
        if (world.getBallRef().getPosition().y < -10) {

          // say all opponents
          if (world.getBodyCycle() % 3 != 0) {
            return SayActionFactory.sayBestTwoOpponents(world.getBodyCycle(),
                world.getOpponents(), ball, player, mf, prophet, world, false);
          }
          else {

            return SayActionFactory.sayPlayerAndOpponent(world.getBodyCycle(),
                world.getSelfRef(), world.getOpponents(), ball, player, mf,
                prophet, world, false);
          }
        }
        // opponents half
        else {
          if (ballSeen) {
            if (playersWithBallControl.size() == 0) {
              str.append(mf.encodeCompleteBall(world.getBallRef(), 0));
              sayAction.setMessage(str.toString());
              return sayAction;
            }
            else {
              return SayActionFactory.sayDribblingPlayerAndOther(world
                  .getBodyCycle(), playersWithBallControl.get(0), world
                  .getOpponents(), world.getBallRef(), player, mf, world,
                  prophet);
            }
          }
          else {
            return SayActionFactory.sayPlayerAndOpponent(world.getBodyCycle(),
                player, world.getOpponents(), ball, player, mf, prophet, world,
                true);
          }
        }
      }
      // we have ball
      else {
        return SayActionFactory.sayPlayerAndOpponent(world.getBodyCycle(),
            player, world.getOpponents(), ball, player, mf, prophet, world,
            true);
      }
    }

    // 6 && 8 assists defense in own half
    if (((world.getSelfRef().getRoleNumber() == 6) || (world.getSelfRef()
        .getRoleNumber() == 8)) && !world.canKick() && state != STATES.INTERCEPT_BALL) {
      if (prophet.inPossessionOfBall() != TEAM.WE) {
        if (world.getBallRef().getPosition().y < -10) {

          // add all opponents
          if (world.getBodyCycle() % 3 == 0) {
            return SayActionFactory.sayBestTwoOpponents(world.getBodyCycle(),
                world.getOpponents(), ball, player, mf, prophet, world, false);
          }
          else {

            return SayActionFactory.sayPlayerAndOpponent(world.getBodyCycle(),
                player, world.getOpponents(), ball, player, mf, prophet, world,
                false);
          }
        }
      }
    }

    switch (state) {

      // goalie says ball when seen
      case GOALIE_DANGER:
      case GOALIE_DEFENSIVE_POSITIONING:
      case GOALIE_MOVE_TO_CATCH:
      case GOALIE_MOVE_TO_WAIT:
      case GOALIE_POSITIONING:
      case GOALIE_MOVE_TO_KICK:
      case GOALIE_NO_DANGER:
      case GOALKEEPER:
        if (ballFullSeen && ballNear) {
          if (playersWithBallControl.size() == 0) {
            str.append(mf.encodeCompleteBall(world.getBallRef(), 0));
          }
          else {
            return SayActionFactory.sayDribblingPlayerAndOther(world
                .getBodyCycle(), playersWithBallControl.get(0), world
                .getOpponents(), ball, player, mf, world, prophet);
          }
        }
        break;

      // defense specific
      case BACKFOUR:
      case BACKFOUR_POSITIONING_NEAR_MIDLINE:
      case MARK_OPPONENT_FROM_BALL:
        if (ballFullSeen && ballNear && world.getBodyCycle() % 2 == 0) {
          if (playersWithBallControl.size() == 0) {
            str.append(mf.encodeCompleteBall(world.getBallRef(), 0));
          }
          else {
            return SayActionFactory.sayDribblingPlayerAndOther(world
                .getBodyCycle(), playersWithBallControl.get(0), world
                .getOpponents(), ball, player, mf, world, prophet);
          }
        }
        else {
          return SayActionFactory.sayPlayerAndOpponent(world.getBodyCycle(),
              player, world.getOpponents(), ball, player, mf, prophet, world,
              false);
        }
        break;

      // dribble specific
      case DRIBBLE:
        return SayActionFactory.sayDribblingPlayerAndOther(
            world.getBodyCycle(), player, world.getOpponents(), ball, player,
            mf, world, prophet);

        // ball specific
      case CLEAR_BALL:
      case GOAL_KICK:
      case GOALIE_KICK_IN:
      case GOALIE_KICK_BALL:
      case KICK_N_RUN:
      case PASS:
      case TACKLE:
      case SHOOT_GOAL:
      case ANTI_KICK_IN_OXSY:
        str.append(mf.encodeKickedBall(world.getBallRef()
            .predictBallAfterAction(world.getSelfRef(), mainAction)));
        break;

      // special
      case INTERCEPT_BALL:
        ball = world.getBallRef();

        // if we will dribble in next cycle
        if (prophet.getInterceptionTime(player) == 1 && prophet
            .getInterceptionTime() == 1) {
          return SayActionFactory.sayDribblingPlayerAndOther(world
              .getBodyCycle(), player, world.getOpponents(), ball, player, mf,
              world, prophet);
        }

        if (!ballFullSeen) {
          return SayActionFactory.sayPlayerAndOpponent(world.getBodyCycle(),
              player, world.getOpponents(), ball, player, mf, prophet, world,
              true);
        }
        else {
          str.append(mf.encodeCompleteBall(ball, 0));
        }
        break;

      case OFFSIDE:
      case BEFORE_KICKOFF:
        return SayActionFactory.sayBestTwoOpponents(world.getBodyCycle(), world
            .getOpponents(), world.getBallRef(), player, mf, prophet, world,
            true);

        // sonstige
      default:
        // we have ball
        if (prophet.inPossessionOfBall() == ControllerNumbers.TEAM.WE) {
          // say nearest opponent (odd cycles) and self (even)
          return SayActionFactory.sayPlayerAndOpponent(world.getBodyCycle(),
              player, world.getOpponents(), ball, player, mf, prophet, world,
              true);
        }
        // opponent has ball
        else {
          if (world.getCycle() % 2 == 0) {
            if (ballFullSeen && ballNear) {
              if (playersWithBallControl.size() > 0) {
                return SayActionFactory.sayDribblingPlayerAndOther(world
                    .getBodyCycle(), playersWithBallControl.get(0), world
                    .getOpponents(), ball, player, mf, world, prophet);
              }
              else {
                str.append(mf.encodeCompleteBall(world.getBallRef(), 0));
              }
            }
            else {
              return SayActionFactory.sayBestTwoOpponents(world.getBodyCycle(),
                  world.getOpponents(), world.getBallRef(), player, mf,
                  prophet, world, false);
            }
          }
        }
    }

    if (str.length() > 0) {
      sayAction.setMessage(str.toString());
      return sayAction;
    }

    return SayActionFactory.sayPlayerAndOpponent(world.getBodyCycle(), player,
        world.getOpponents(), ball, player, mf, prophet, world, true);
  }

  /**
   * returns a list off opponents age 0 that are near enough to kick the ball.
   * 
   * @param wm -
   *          world model
   * @return - list of opponents with ball control
   */
  private static LinkedList<Player> getSeenOpponentsThatCanKick(final WorldModel wm) {

    // no dribbling without ball
    if (wm.getBallRef().getAge(wm.getBodyCycle(),
        robocup.component.worldobjects.Ball.UPDATE_MODE.SEEN_FULL) > 0) {
      return new LinkedList<Player>();
    }

    final LinkedList<Player> opponents = wm.getPlayersThatCanKick(wm.getBallRef(), wm
        .getOpponents());
    final LinkedList<Player> result = new LinkedList<Player>();

    for (final Player o : opponents) {
      if (o.getAge(wm.getBodyCycle(), UPDATE_MODE.SEEN_FULL) == 0) {
        result.add(o);
      }
    }

    return result;
  }

  /**
   * chooses randomly one of the given players that has been fully seen in this
   * cycle.
   * 
   * @param bodyCycle -
   *          this bodyCycle
   * @param players -
   *          an array of Players to choose from
   * @return the chosen player, null if none of the players was fully seen
   */
  private static LinkedList<Player> getFullySeenPlayersByRandom(final int bodyCycle,
      final Player[] players,
      final Ball b,
      final Player self,
      final WorldModel world,
      final Prophet prophet) {

    final LinkedList<WeightedPlayer> seenPlayers = new LinkedList<WeightedPlayer>();
    final LinkedList<Player> randomPlayers = new LinkedList<Player>();

    for (final Player p : players) {
      if (p.getAge(bodyCycle, UPDATE_MODE.SEEN_FULL) < 2 || (p.getAge(
          bodyCycle, UPDATE_MODE.SEEN_FULL) < 2 && p.getDistance(self) < 4)) {
        seenPlayers.add(new WeightedPlayer(p));
      }
    }

    for (int i = 0; i < seenPlayers.size(); i++) {
      seenPlayers.get(i).weight = 0;

      // prefer players near ball and me (those between these 2 points are most
      // interesting)
      seenPlayers.get(i).weight += Math.pow(b
          .getDistance(seenPlayers.get(i).player), 3);
      seenPlayers.get(i).weight += Math.pow(self
          .getDistance(seenPlayers.get(i).player), 2);

      // prefer young
      seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(bodyCycle,
          UPDATE_MODE.SEEN_FULL) == 1 ? 2.0 : 1.0;

      if (prophet.inPossessionOfBall() == TEAM.WE) {
        final Player fastest = prophet.getFastestTeammateToBall();

        if (fastest != null) {

          // players that can be seen by the dribbling player aren't said
          if (fastest.getDistance(seenPlayers.get(i).player) < 3) {
            seenPlayers.get(i).weight *= 4;
          }
          else {
            seenPlayers.get(i).weight /= (Math.abs(fastest.getAngleAbs(
                seenPlayers.get(i).player, RELATIVE.TO_BODY)) > 100) ? 10 : 1;
          }
        }

        // prefer near opponents so the teammate with ball know if he can pass
        // to me
        seenPlayers.get(i).weight /= seenPlayers.get(i).player
            .getDistance(self) < 7 ? 5 : 1;

        // prefer opponents that mgiht define the offside line
        seenPlayers.get(i).weight /= (seenPlayers.get(i).player.getPosition().y > world
            .getOffsideY(true) - 2 && b.getPosition().y > world
            .getOffsideY(true) - 15) ? 5 : 1;

        // prefer goalie when near goal
        seenPlayers.get(i).weight /= seenPlayers.get(i).player.isGoalie() && world
            .inOpponentsPenalty(prophet.getNextBallInterceptionPoint()) ? 5 : 1;
      }

      seenPlayers.get(i).weight *= Math.random();
    }

    Collections.sort(seenPlayers);

    for (final WeightedPlayer p : seenPlayers) {
      randomPlayers.add(p.player);
    }

    return randomPlayers;
  }

  /**
   * chooses randomly one of the given players that has been fully seen in this
   * cycle.
   * 
   * @param bodyCycle -
   *          this bodyCycle
   * @param players -
   *          an array of Players to choose from
   * @return the chosen player, null if none of the players was fully seen
   */
  private static LinkedList<Player> getFullySeenPlayersByRandomDefense(final int bodyCycle,
      final Player[] players,
      final Ball b,
      final Player self,
      final Prophet prophet,
      final WorldModel world) {

    final Player[] opps = world.getOpponents();
    LinkedList<Player> randomPlayers = new LinkedList<Player>();
    for (Player p : opps) {
		if (p.exists() && p.getPosition().getY() < -15) {
			randomPlayers.add(p);
		}
    }
    
    for (final Player def : world.getTeammates()) {
      if (def.getNumber() > 1 && def.getNumber() < 6) {
        randomPlayers.add(def);
      }
    }

    final ROLE myRole = self.getDefaultRole();
    final LinkedList<WeightedPlayer> seenPlayers = new LinkedList<WeightedPlayer>();

    for (final Player p : randomPlayers) {
      if (p == null) {
        // seenPlayers.add(new WeightedPlayer(null));
      }
      else if (p.getAge(bodyCycle, UPDATE_MODE.SEEN_FULL) < 3 || (p.getAge(
          bodyCycle, UPDATE_MODE.SEEN_NOTEAM) < 3 && p.getDistance(self) < 4)) {
        seenPlayers.add(new WeightedPlayer(p));
      }
    }

    switch (myRole) {
      case CENTER_MIDFIELD:
        for (int i = 0; i < seenPlayers.size(); i++) {
          seenPlayers.get(i).weight = 0;
          if (!seenPlayers.get(i).player.isFriend()) {
            seenPlayers.get(i).weight = 30;
            seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(
                bodyCycle, UPDATE_MODE.META_BASED_ON_INFO) > 2 ? 0.5 : 1.0;
            if (prophet.isFastestPlayerToBall(seenPlayers.get(i).player)) {
              seenPlayers.get(i).weight *= 0.9;
            }
          }
          if (seenPlayers.get(i).player.isFriend()) {
            seenPlayers.get(i).weight = 10;
            seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(
                bodyCycle, UPDATE_MODE.META_BASED_ON_INFO) > 2 ? 0.5 : 1.0;
          }
        }
        break;
      case LEFT_MIDFIELD:
        for (int i = 0; i < seenPlayers.size(); i++) {
          seenPlayers.get(i).weight = 0;
          if (!seenPlayers.get(i).player.isFriend()) {
            seenPlayers.get(i).weight = 30;
            seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(
                bodyCycle, UPDATE_MODE.META_BASED_ON_INFO) > 2 ? 0.5 : 1.0;
            if (prophet.isFastestPlayerToBall(seenPlayers.get(i).player)) {
              seenPlayers.get(i).weight *= 0.9;
            }
          }
          if (seenPlayers.get(i).player.isFriend()) {
            seenPlayers.get(i).weight = 10;
            seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(
                bodyCycle, UPDATE_MODE.META_BASED_ON_INFO) > 2 ? 0.5 : 1.0;
          }
        }

        break;
      case RIGHT_MIDFIELD:
        for (int i = 0; i < seenPlayers.size(); i++) {
          seenPlayers.get(i).weight = 0;
          if (!seenPlayers.get(i).player.isFriend()) {
            seenPlayers.get(i).weight = 30;
            seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(
                bodyCycle, UPDATE_MODE.META_BASED_ON_INFO) > 2 ? 0.5 : 1.0;
            if (prophet.isFastestPlayerToBall(seenPlayers.get(i).player)) {
              seenPlayers.get(i).weight *= 0.9;
            }
          }
          if (seenPlayers.get(i).player.isFriend()) {
            seenPlayers.get(i).weight = 10;
            seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(
                bodyCycle, UPDATE_MODE.META_BASED_ON_INFO) > 2 ? 0.5 : 1.0;
          }
        }
        break;
      default:
        for (int i = 0; i < seenPlayers.size(); i++) {
          seenPlayers.get(i).weight = 0;
          if (!seenPlayers.get(i).player.isFriend()) {
            seenPlayers.get(i).weight = 30;
            seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(
                bodyCycle, UPDATE_MODE.META_BASED_ON_INFO) > 2 ? 0.5 : 1.0;
            if (prophet.isFastestPlayerToBall(seenPlayers.get(i).player)) {
              seenPlayers.get(i).weight *= 0.9;
            }
          }
          if (seenPlayers.get(i).player.isFriend()) {
            seenPlayers.get(i).weight = 10;
            seenPlayers.get(i).weight *= seenPlayers.get(i).player.getAge(
                bodyCycle, UPDATE_MODE.META_BASED_ON_INFO) > 2 ? 0.5 : 1.0;
          }
        }
    }
    Collections.sort(seenPlayers);
    randomPlayers.clear();
    for (final WeightedPlayer p : seenPlayers) {
      if (p.player != null) {
        randomPlayers.add(p.player);
      }
    }
    return randomPlayers;
  }

  /**
   * says the best two seen opponents. "best" is defined by the
   * "getFullySeenPlayersByRandom" - method.
   * 
   * @param bodyCycle -
   *          the current body cycle
   * @param opponents -
   *          the oppoennts to choose from
   * @param ball -
   *          the ball
   * @param self -
   *          the self
   * @param mf -
   *          the message factory
   * @param prophet -
   *          the prophet
   * @return the say action
   */
  private static SayAction sayBestTwoOpponents(final int bodyCycle,
      final Player[] opponents,
      final Ball ball,
      final Player self,
      final MessageFactory mf,
      final Prophet prophet,
      final WorldModel world,
      final boolean offense) {

    final StringBuffer str = new StringBuffer();

    LinkedList<Player> seenPlayers = new LinkedList<Player>();

    if (offense) {
      seenPlayers = SayActionFactory.getFullySeenPlayersByRandom(bodyCycle,
          opponents, ball, self, world, prophet);
    }
    else {
      seenPlayers = SayActionFactory.getFullySeenPlayersByRandomDefense(
          bodyCycle, opponents, ball, self, prophet, world);
    }

    if (offense) {
      switch (seenPlayers.size()) {
        case 0:
          str.append(mf.encodeCompletePlayer(self, 1));
          break;
        case 1:
          if (prophet.getPlayersThatCanKick().contains(seenPlayers.get(0))) {
            return SayActionFactory.sayDribblingPlayerAndSelf(seenPlayers
                .get(0), self, mf);
          }

          str.append(mf.encodePlayerPositionAndStamina(self));
          str.append(mf.encodePlayerPosition(seenPlayers.get(0), 0));
          break;
        default:
          if (prophet.getPlayersThatCanKick().contains(seenPlayers.get(0))) {
            return SayActionFactory.sayDribblingPlayerAndSelf(seenPlayers
                .get(0), seenPlayers.get(1), mf);
          }

          if (prophet.getPlayersThatCanKick().contains(seenPlayers.get(1))) {
            return SayActionFactory.sayDribblingPlayerAndSelf(seenPlayers
                .get(1), seenPlayers.get(0), mf);
          }

          str.append(mf.encodePlayerPosition(seenPlayers.get(0), 0));
          str.append(mf.encodePlayerPosition(seenPlayers.get(1), 0));
      }
    }
    else {
      switch (seenPlayers.size()) {
        case 0:
          str.append(mf.encodeCompletePlayer(self, 1));
          break;
        case 1:
          str.append(mf.encodePlayerPositionAndStamina(self));
          str.append(mf.encodePlayerPosition(seenPlayers.get(0), 0));
          break;
        default:
          str.append(mf.encodePlayerPosition(seenPlayers.get(0), 0));
          str.append(mf.encodePlayerPosition(seenPlayers.get(1), 0));
      }
    }

    return new SayAction(str.toString());
  }

  /**
   * says the dribbling player (and therefor the ball) and the best opponent
   * "best" is defined by the "getFullySeenPlayersByRandom" - method.
   * 
   * @param bodyCycle -
   *          the bodycycle
   * @param dribbler -
   *          the player who dribbles
   * @param opponents -
   *          the oppoennts to choose from
   * @param ball -
   *          the ball
   * @param self -
   *          the self
   * @param mf -
   *          the message factory
   * @param prophet -
   *          the prophet
   * @return the say action
   */
  private static SayAction sayDribblingPlayerAndOther(final int bodyCycle,
      final Player dribbler,
      final Player[] opponents,
      final Ball ball,
      final Player self,
      final MessageFactory mf,
      final WorldModel world,
      final Prophet prophet) {

    final StringBuffer str = new StringBuffer();

    final int age = dribbler.isMe() ? 1 : 0;

    final LinkedList<Player> seenPlayers = SayActionFactory
        .getFullySeenPlayersByRandom(bodyCycle, opponents, ball, self, world,
            prophet);

    switch (seenPlayers.size()) {

      case 0:
        str.append(mf.encodeDribblePlayer(dribbler, self, age));
        break;

      case 1:
        if (dribbler.equals(seenPlayers.get(0))) {
          str.append(mf.encodeDribblePlayer(dribbler, self, age));
        }
        else {
          str.append(mf.encodeDribblePlayer(dribbler, seenPlayers.get(0), age));
        }
        break;

      default:
        if (dribbler.equals(seenPlayers.get(0))) {
          str.append(mf.encodeDribblePlayer(dribbler, seenPlayers.get(1), age));
        }
        else {
          str.append(mf.encodeDribblePlayer(dribbler, seenPlayers.get(0), age));
        }
        break;
    }

    return new SayAction(str.toString());
  }

  /**
   * says the dribbling player (and therefor the ball) and the best opponent
   * "best" is defined by the "getFullySeenPlayersByRandom" - method.
   * 
   * @param bodyCycle -
   *          the bodycycle
   * @param dribbler -
   *          the player who dribbles
   * @param opponents -
   *          the oppoennts to choose from
   * @param ball -
   *          the ball
   * @param self -
   *          the self
   * @param mf -
   *          the message factory
   * @param prophet -
   *          the prophet
   * @return the say action
   */
  private static SayAction sayDribblingPlayerAndSelf(final Player dribbler,
      final Player self,
      final MessageFactory mf) {

    final StringBuffer str = new StringBuffer();

    str.append(mf.encodeDribblePlayer(dribbler, self, 0));

    return new SayAction(str.toString());
  }

  /**
   * says a player (most often the self) and the best opponent "best" is defined
   * by the "getFullySeenPlayersByRandom" - method.
   * 
   * @param bodyCycle -
   *          the bodycycle
   * @param player -
   *          the player to say
   * @param opponents -
   *          the oppoennts to choose from
   * @param ball -
   *          the ball
   * @param self -
   *          the self
   * @param mf -
   *          the message factory
   * @param prophet -
   *          the prophet
   * @return the say action
   */
  private static SayAction sayPlayerAndOpponent(final int bodyCycle,
      final Player player,
      final Player[] opponents,
      final Ball b,
      final Player self,
      final MessageFactory mf,
      final Prophet prophet,
      final WorldModel world,
      final boolean offense) {

    final StringBuffer str = new StringBuffer();

    LinkedList<Player> seenPlayers;
    if (offense) {
      seenPlayers = SayActionFactory.getFullySeenPlayersByRandom(bodyCycle,
          opponents, b, self, world, prophet);
    }
    else {
      seenPlayers = SayActionFactory.getFullySeenPlayersByRandomDefense(
          bodyCycle, opponents, b, self, prophet, world);
    }

    switch (seenPlayers.size()) {

      case 0:
        str.append(mf.encodeCompletePlayer(player, 0));
        break;

      case 1:
        if (prophet.getPlayersThatCanKick().contains(seenPlayers.get(0))) {
          return SayActionFactory.sayDribblingPlayerAndSelf(seenPlayers.get(0),
              self, mf);
        }

        if (player.equals(seenPlayers.get(0))) {
          str.append(mf.encodeCompletePlayer(player, 0));
        }
        else {
          if (player.isMe()) {
            str.append(mf.encodePlayerPositionAndStamina(player));
          }
          else {
            str.append(mf.encodePlayerPosition(player, 0));
          }
          str.append(mf.encodePlayerPosition(seenPlayers.get(0), 0));
        }
        break;

      default:
        if (prophet.getPlayersThatCanKick().contains(seenPlayers.get(0))) {
          return SayActionFactory.sayDribblingPlayerAndSelf(seenPlayers.get(0),
              seenPlayers.get(1), mf);
        }

        if (prophet.getPlayersThatCanKick().contains(seenPlayers.get(1))) {
          return SayActionFactory.sayDribblingPlayerAndSelf(seenPlayers.get(1),
              seenPlayers.get(0), mf);
        }

        if (player.equals(seenPlayers.get(0))) {
          str.append(mf.encodePlayerPosition(player, 0));
          str.append(mf.encodePlayerPosition(seenPlayers.get(1), 0));
        }
        else {
          if (player.isMe()) {
            str.append(mf.encodePlayerPositionAndStamina(player));
          }
          else {
            str.append(mf.encodePlayerPosition(player, 0));
          }
          str.append(mf.encodePlayerPosition(seenPlayers.get(0), 0));
        }
    }

    return new SayAction(str.toString());
  }

}
